package mt.comm.implement;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import mt.Order;
import mt.comm.implement.ClientSideMessage.Type;

public class ServerCommImp implements ServerComm {

	private int PORT = 8080;
	private ServerSocket serverSocket;
	/** The clients that are connected to the Client of MicroTrader. */
	private BlockingQueue<ServerSideMessageImp> messagesFromClientToServer = new LinkedBlockingQueue<ServerSideMessageImp>();
	private HashMap<String, ClientHandler> connectedClients = new HashMap<String, ClientHandler>();
	private ServerCommImp server = this;
	ClientHandler handler;

	/**
	 * Starts the MicroTrader server. The method should setup a server socket
	 * and begin to accept connections. This method must return immediately
	 * after a server socket has been setup, and another thread should started
	 * to listen for connections.{}
	 * 
	 */
	public void start() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					serverSocket = new ServerSocket(PORT);
					while (true) {
						Socket socket = serverSocket.accept();
						System.out.println("acceptiong");
						ClientHandler aux = new ClientHandler(socket, server);
						aux.start();
					}
				} catch (IOException e) {
					System.out.println("Server already running");
					e.printStackTrace();
				}

			}

		}).start();

	}

	/**
	 * Get the next message received from one of the clients. If no message is
	 * has been received, the method blocks until a message is received. If no
	 * message will ever be received, <b>null</b> is returned.
	 * 
	 * @return The next message sent by one of the connected clients to the
	 *         server
	 */
	@Override
	public ServerSideMessageImp getNextMessage() {
		ServerSideMessageImp aux = null;
		try {
			aux = (ServerSideMessageImp) messagesFromClientToServer.take();
			System.out.println("Server recieved message from " + aux.getSenderNickname() );

		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return aux;
	}

	/**
	 * Checks if a message from a client is pending. If
	 * {@link #hasNextMessage()} returns <b>true</b>, a call to
	 * {@link #getNextMessage} will return immediately with the oldest message
	 * in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call
	 * to {@link #getNextMessage} will block until a message has been received.
	 * 
	 * @return <b>true</b> if a message from a client is currently pending,
	 *         otherwise <b>false</b>.
	 */
	public boolean hasNextMessage() {
		return !getMessagesFromClientToServer().isEmpty();
	}

	/**
	 * Send an order to a client.
	 * 
	 * @param receiversNickname
	 *            nickname of the client to receive the order. If no client with
	 *            the nickname is currently connected, no order is sent.
	 * @param order
	 *            order to send.
	 */
	public void sendOrder(String receiversNickname, Order order) {
		ClientHandler aux = getTargetClient(receiversNickname);
		if (aux != null) {
			System.out.println("COntem cliente no servidor: "+getConnectedClients().toString());
			ClientSideMessageImp message = new ClientSideMessageImp(Type.ORDER,
					order, null);
			aux.getMessagesFromServerToClient().add(message);
		}

	}

	/**
	 * Report an error to a client.
	 * 
	 * @param toNickname
	 *            nickname of the client.
	 * @param error
	 *            description of the error.
	 */
	public void sendError(String toNickname, String error) {
		ClientHandler aux = getTargetClient(toNickname);
		if (aux != null) {
			ClientSideMessageImp message = new ClientSideMessageImp(Type.ERROR,
					null, error);
			System.out.println("ERROR MESSAGE SENT");
			aux.getMessagesFromServerToClient().add(message);
		}
	}

	/**
	 * Checks if a client with a certain nickname is currently connected.
	 * 
	 * @param nickname
	 *            nickname of the client to check.
	 * @return <b>true</b> if a client with the nickname is currently connected,
	 *         otherwise <b>false</b>.
	 */
	public boolean clientIsConnected(String nickname) {
		if (!connectedClients.containsKey(nickname))
			return false;
		return true;
	}

	// private ClientHandler getHandler() {
	// return handler;
	// }

	/**
	 * Disconnect a client.
	 * 
	 * @param nickname
	 *            nickname of the client to disconnect.
	 */
	public void disconnectClient(String nickname) {
		connectedClients.remove(nickname);

	}


	public BlockingQueue<ServerSideMessageImp> getMessagesFromClientToServer() {
		return messagesFromClientToServer;
	}

	/**
	 * Returns the instantiation of the class ClientHandler that is associated
	 * to the nickname that is given.
	 * 
	 * @param nickname
	 *            , this nickname is used to search through an HashMap that
	 *            contains the ClientHandler instantiations associated to each
	 *            user. The users are represented by their nickname.
	 * @return the ClientHandler that is associated to the nickname given.
	 */

	private ClientHandler getTargetClient(String nickname) {
		ClientHandler aux = getConnectedClients().get(nickname);
		return aux;
	}

	public HashMap<String, ClientHandler> getConnectedClients() {
		return connectedClients;
	}
}
